using System;
using System.IO;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using DevExpress.CodeRush.Core;
using DevExpress.CodeRush.PlugInCore;
using DevExpress.CodeRush.StructuralParser;
using System.Reflection;
using Reflector;
using Reflector.CodeModel;
using EnvDTE;

namespace TestMechanic.Net
{
    public delegate void TestMethodActivatedEventHandler(TestMethodActivatedEventArgs eventArgs);

    public class TestCoverageEventManager
    {
        
#region Fields

        private CodeRushEvents _DXCoreEvents;
        private string _PreviousMethodName;
        private string _TestSubjectMemberName;
        private string _TestSubjectClassName;
        private string _TestSubjectAssemblyName;
        private string _TestSubjectNamespace;
        private IAssemblyManager _AssemblyLoader;
        private IServiceProvider _ServiceProvider;
        private TypeReferenceExpression _TestSubjectTypeRef;
        private IElement _TestSubjectElement;

#endregion
        
#region Properties

        private string BinDirectory
        {
            get
            {
                string OutputDirectory;
                OutputDirectory = (string)CodeRush.Solution.Active.DTE.ActiveDocument.ProjectItem.ContainingProject.ConfigurationManager.ActiveConfiguration.Properties.Item(13).Value;
                OutputDirectory = Path.Combine(ProjectDirectory, OutputDirectory);
                return OutputDirectory;
            }
        }

        private string ProjectDirectory
        {
            get
            {
                string ProjectFilePath;
                string ProjectDirectory;
                ProjectFilePath = CodeRush.Solution.Active.DTE.ActiveDocument.ProjectItem.ContainingProject.FileName;
                ProjectDirectory = Path.GetDirectoryName(ProjectFilePath);
                return ProjectDirectory;
            }
        }

        public IServiceProvider ServiceProvider
        {
            get
            {
                if (_ServiceProvider == null)
                    _ServiceProvider = new Reflector.Application(null);
                return _ServiceProvider;
            }
        }

        private IAssemblyManager AssemblyLoader
        {
            get
            {
                _AssemblyLoader = (IAssemblyManager)this.ServiceProvider.GetService(typeof(IAssemblyManager));
                return _AssemblyLoader;
            }
        }

        public string TestSubjectMemberName
        {
            get
            {
                if (_TestSubjectMemberName == null || _TestSubjectMemberName == String.Empty)
                {
                    foreach (DevExpress.CodeRush.StructuralParser.Attribute CurrentAttribute in CodeRush.Source.ActiveMethod.Attributes)
                    {
                        if (typeof(TestCoverage.TestSubjectMemberAttribute).Name == String.Format("{0}Attribute", CurrentAttribute.Name))
                        {
                            _TestSubjectMemberName = (String)CurrentAttribute.Arguments[0].Evaluate();
                            break;
                        }
                    }
                }
                return _TestSubjectMemberName;
            }
            set
            {
                _TestSubjectMemberName = value;
            }
        }

        public string TestSubjectClassName
        {
            get
            {
                if (_TestSubjectClassName == null || _TestSubjectClassName == String.Empty)
                {
                    _TestSubjectClassName = this.TestSubjectTypeRef.Name;
                }
                return _TestSubjectClassName;
            }
            set
            {
                _TestSubjectClassName = value;
            }
        }

        public TypeReferenceExpression TestSubjectTypeRef
        {
            get
            {
                if (_TestSubjectTypeRef == null)
                {
                    foreach (DevExpress.CodeRush.StructuralParser.Attribute CurrentAttribute in CodeRush.Source.ActiveClass.Attributes)
                    {
                        if (typeof(TestCoverage.TestSubjectClassAttribute).Name == String.Format("{0}Attribute", CurrentAttribute.Name))
                        {
                            _TestSubjectTypeRef = ((DevExpress.CodeRush.StructuralParser.TypeOfExpression)CurrentAttribute.Arguments[0]).TypeReference;
                            break;
                        }
                    }
                }
                return _TestSubjectTypeRef;
            }
            set
            {
                _TestSubjectTypeRef = value;
            }
        }

        public string TestSubjectAssemblyName
        {
            get
            {
                if (_TestSubjectAssemblyName == null || _TestSubjectAssemblyName == String.Empty)
                {
                    string AssemblyInfoFileName = string.Format("AssemblyInfo{0}", Path.GetExtension(CodeRush.File.Active));
                    CodeRush.Source.ActiveProject.LoadDiskFiles(CodeRush.Solution.Active.DTE.ActiveDocument.ProjectItem.ContainingProject);
                    SourceFile AssemblyInfoFile = null;
                    foreach (SourceFile File in CodeRush.Source.ActiveProject.DiskFiles)
                    {
                        if (File.Name.EndsWith(AssemblyInfoFileName))
                        {
                            AssemblyInfoFile = File;
                            break;
                        }
                    }

                    foreach (object Item in AssemblyInfoFile.Nodes)
                    {
                        if (Item is DevExpress.CodeRush.StructuralParser.AttributeSection)
                        {
                            foreach (DevExpress.CodeRush.StructuralParser.Attribute CurrentAttribute in ((DevExpress.CodeRush.StructuralParser.AttributeSection)Item).AttributeCollection)
                            {
                                if (typeof(TestCoverage.TestSubjectAssemblyAttribute).Name == String.Format("{0}Attribute", CurrentAttribute.Name))
                                {
                                    _TestSubjectAssemblyName = (String)CurrentAttribute.Arguments[0].Evaluate();
                                }
                            }
                        }
                    }
                }
                return _TestSubjectAssemblyName;
            }
            set
            {
                _TestSubjectAssemblyName = value;
            }
        }

        public string TestSubjectNamespace
        {
            get
            {
                if (_TestSubjectNamespace == null || _TestSubjectNamespace == String.Empty)
                {
                    System.Text.RegularExpressions.Regex Rx = new System.Text.RegularExpressions.Regex(@"\.\w+$");
                    _TestSubjectNamespace = Rx.Replace(this.TestSubjectElement.RootNamespaceFullName, "");
                }
                return _TestSubjectNamespace;
            }
            set
            {
                _TestSubjectNamespace = value;
            }
        }

        public IElement TestSubjectElement
        {
            get
            {
                if (_TestSubjectElement == null)
                {
                    ISourceTreeResolver Resolver = ParserServices.SourceTreeResolver;
                    _TestSubjectElement = Resolver.ResolveType(this.TestSubjectTypeRef);
                }
                return _TestSubjectElement;
            }
            set
            {
                _TestSubjectElement = value;
            }
        }

        public string PreviousMethodName
        {
            get
            {
                return _PreviousMethodName;
            }
            set
            {
                _PreviousMethodName = value;
            }
        }

        public CodeRushEvents DXCoreEvents
        {
            get
            {
                if (_DXCoreEvents == null)
                    _DXCoreEvents = new CodeRushEvents();
                return _DXCoreEvents;
            }
            set
            {
                _DXCoreEvents = value;
            }
        }

#endregion
        
#region Events

        public event TestMethodActivatedEventHandler TestMethodActivated;

#endregion
        
#region Constructors

        public TestCoverageEventManager()
        {
            
        }

#endregion
        
#region Activation

        public void Activate()
        {
            HintDirectories.Add(Path.GetDirectoryName(this.GetType().Assembly.Location));
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(this.AssemblyResolve);
            this.DXCoreEvents.LanguageElementActivated += new LanguageElementActivatedEventHandler(this.LanguageElementActivatedEventHandled);
        }

        public void Deactivate()
        {
            this.DXCoreEvents.LanguageElementActivated -= new LanguageElementActivatedEventHandler(this.LanguageElementActivatedEventHandled);
            AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(this.AssemblyResolve);
        }

#endregion
        
#region Event Handlers

        public void LanguageElementActivatedEventHandled(LanguageElementActivatedEventArgs eventArgs)
        {
            if (eventArgs.Element.InsideMethod
                && CodeRush.Source.ActiveMemberName != this.PreviousMethodName
                && this.HasTestCoverage(CodeRush.Source.ActiveMethod.Attributes))
            {
                this.PreviousMethodName = CodeRush.Source.ActiveMemberName;

                TestMethodActivatedEventArgs EventArgs = new TestMethodActivatedEventArgs();
                EventArgs.TestSubjectAssemblyName = this.TestSubjectAssemblyName;
                EventArgs.TestSubjectClassName = this.TestSubjectClassName;
                EventArgs.TestSubjectElement = this.TestSubjectElement;
                EventArgs.TestSubjectMemberName = this.TestSubjectMemberName;
                EventArgs.TestSubjectNamespace = this.TestSubjectNamespace;
                EventArgs.TestSubjectTypeRef = this.TestSubjectTypeRef;

                this.TestMethodActivated(EventArgs);
            }
        }

#endregion
        
#region Helpers

        public Boolean HasTestCoverage(NodeList attributes)
        {
            foreach (DevExpress.CodeRush.StructuralParser.Attribute CurrentAttribute in attributes)
            {
                if (typeof(TestCoverage.TestSubjectMemberAttribute).Name == String.Format("{0}Attribute", CurrentAttribute.Name))
                {
                    this.TestSubjectMemberName = (String)CurrentAttribute.Arguments[0].Evaluate();
                    return true;
                }
                else
                {
                    this.TestSubjectAssemblyName = String.Empty;
                    this.TestSubjectClassName = String.Empty;
                    this.TestSubjectMemberName = String.Empty;
                    this.TestSubjectNamespace = String.Empty;
                    this.TestSubjectTypeRef = null;
                }
            }
            return false;
        }

#endregion

#region AssemblyLoader Resolution

        private StringCollection HintDirectories = new StringCollection();

        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
        {
            char[] Separators = new char[1] { ',' };
            string AsmName = args.Name.Split(Separators)[0];
            foreach (string Directory in HintDirectories)
            {
                string AsmPath = Path.Combine(Directory, string.Format("{0}.dll", AsmName));
                if (System.IO.File.Exists(AsmPath))
                {
                    return Assembly.LoadFrom(AsmPath);
                }
                AsmPath = Path.Combine(Directory, string.Format("{0}.exe", AsmName));
                if (System.IO.File.Exists(AsmPath))
                {
                    return Assembly.LoadFrom(AsmPath);
                }
            }
            return null;
        }

#endregion

    }

    public class TestMethodActivatedEventArgs : EventArgs
    {
        
#region Fields

        private TypeReferenceExpression _TestSubjectTypeRef;
        private IElement _TestSubjectElement;
        private string _TestSubjectMemberName;
        private string _TestSubjectClassName;
        private string _TestSubjectAssemblyName;
        private string _TestSubjectNamespace;

#endregion
        
#region Properties

        public TypeReferenceExpression TestSubjectTypeRef
        {
            get
            {
                return _TestSubjectTypeRef;
            }
            set
            {
                _TestSubjectTypeRef = value;
            }
        }

        public IElement TestSubjectElement
        {
            get
            {
                return _TestSubjectElement;
            }
            set
            {
                _TestSubjectElement = value;
            }
        }

        public string TestSubjectMemberName
        {
            get
            {
                return _TestSubjectMemberName;
            }
            set
            {
                _TestSubjectMemberName = value;
            }
        }

        public string TestSubjectClassName
        {
            get
            {
                return _TestSubjectClassName;
            }
            set
            {
                _TestSubjectClassName = value;
            }
        }

        public string TestSubjectAssemblyName
        {
            get
            {
                return _TestSubjectAssemblyName;
            }
            set
            {
                _TestSubjectAssemblyName = value;
            }
        }

        public string TestSubjectNamespace
        {
            get
            {
                return _TestSubjectNamespace;
            }
            set
            {
                _TestSubjectNamespace = value;
            }
        }

#endregion

    }

}
